#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <time.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

#include <arpa/inet.h>

clock_t start_clock;
struct icmpheader {
  unsigned char icmp_type;
  unsigned char icmp_code;
  unsigned short int icmp_cksum;
  unsigned short int icmp_id;
  unsigned short int icmp_seq;
}; 

int main(int argc, char** argv) {
  if (argc != 2) {
    printf("Usage: ./tracert <address>\n");
    return 0;
  }
  char* hname = argv[1];
  for (int i = 1; i <= 64; ++i) {
    int ttl = i;
    hostent *host = gethostbyname(hname);
    if (host == NULL) { 
      perror("wrong host name");
      return 1;
    }
  
    udphdr udp;
    udp.source = htons(33434);
    udp.dest = htons(53);
    udp.len = htons(8);
  
    int sd;
    if ((sd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0) {
      perror("raw socket");
      return 1;
    }
  
    setsockopt(sd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
        
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = ((in_addr*)host->h_addr)->s_addr;
	
    timespec time1;
    timespec time2;
    clock_gettime(CLOCK_MONOTONIC, &time1);

    int size = 0;
    if ((size = sendto(sd, &udp, sizeof(udp), 0, 
                       (sockaddr *)&sin, sizeof(sockaddr))) < 0) {
      perror("sendto");
      return 1;
    }
	
    sockaddr_in sout;
    sout.sin_family = AF_INET;
    sout.sin_port = 0;
    sout.sin_addr.s_addr = INADDR_ANY;
	
    int rcv_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); 
    if (bind(rcv_socket, (sockaddr *)&sout, sizeof(sout)) == -1) {
      perror("bind");
    }
    timeval timeout;
    timeout.tv_sec = 30;
    timeout.tv_usec = 0;
    setsockopt(rcv_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); 
	
    char buffer[300];
    socklen_t a = sizeof(sout);
    size = recvfrom(rcv_socket, &buffer, 300,  0, (sockaddr *)&sout, &a);
    if (size == -1) {
      printf("%d * * *\n", ttl);
      continue;
    }
    icmpheader icmphdr;
    memcpy(&icmphdr, buffer + 20, sizeof(icmpheader)); 
    clock_gettime(CLOCK_MONOTONIC, &time2);
    if (icmphdr.icmp_type == 11) {
      printf("%d\t%s\t%f sec\n", ttl, inet_ntoa(sout.sin_addr), 
             (time2.tv_sec - time1.tv_sec) + 
             (time2.tv_nsec - time1.tv_nsec) / 1.0e9);
      continue;
    } else if ((icmphdr.icmp_type == 3) && (icmphdr.icmp_code == 10)) {
      printf("%d * * *\n", ttl);
      continue;
    } else if ((icmphdr.icmp_type == 3) && (icmphdr.icmp_code == 1)
               || (icmphdr.icmp_type == 0)) {
      printf("%d\t%s\t%f sec\n", ttl, hname, 
             (time2.tv_sec - time1.tv_sec) + 
             (time2.tv_nsec - time1.tv_nsec) / 1.0e9);
      printf("Tracert is finished!\n");
      return 0;
    }
  }
  printf("Tracert is not finished!\n");
  return 0;
}

